package com.ruoyi.oss.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProvider;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.aliyun.listener.PutObjectProgressListener;
import com.ruoyi.oss.config.properties.OssProperties;
import com.ruoyi.oss.domain.SysOss;
import com.ruoyi.oss.domain.bo.UploadResult;
import com.ruoyi.oss.ienum.OssType;
import com.ruoyi.oss.service.CloudOssService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class AliYunCloudOssServiceImpl implements CloudOssService {

    private final OssProperties ossProperties;

    private final OssProperties.AliYunProperties aliYunProperties;


    public AliYunCloudOssServiceImpl(OssProperties ossProperties) {
        this.ossProperties = ossProperties;
        this.aliYunProperties = ossProperties.getAliyun();
    }

    /**
     * 包装 上传文件结果
     *
     * @param endpoint   oss区域端点
     * @param bucketName 桶
     * @param path       路径
     * @return 上传文件的结果
     */
    private static UploadResult getUploadResult(String endpoint, String bucketName, String path) {
        UploadResult uploadResult = new UploadResult(OssType.ALIYUN);
        String urlStr;
        if (endpoint.contains("http://")) {
            urlStr = endpoint.replace("http://", "http://" + bucketName + ".");
        } else {
            urlStr = endpoint.replace("https://", "https://" + bucketName + ".");
        }
        urlStr = urlStr + "/" + path;
        uploadResult.setUrl(urlStr);
        uploadResult.setOssPath(path);
        String fileName = path.substring(path.lastIndexOf('/') + 1);
        uploadResult.setFileName(fileName);
        return uploadResult;
    }

    /**
     * 上传文件至OSS
     *
     * @param multipartFile 多部份文件
     * @param fileSuffix    文件后缀
     * @return 上传文件的结果
     */
    @Override
    public UploadResult upload(MultipartFile multipartFile, String fileSuffix) throws IOException {
        OSS ossClient = getOssClient();
        String endpoint = aliYunProperties.getEndpoint();
        String bucketName = aliYunProperties.getBucketName();
        String prefix = aliYunProperties.getPrefix();
        String path = getPath(prefix, fileSuffix);
        try (InputStream inputStream = multipartFile.getInputStream()) {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, path, inputStream);
            putObjectRequest.withProgressListener(new PutObjectProgressListener());
            // 上传
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            if (log.isDebugEnabled()) {
                log.debug("文件上传成功,文件响应信息ETag:{}", result.getETag());
            }
            UploadResult uploadResult = getUploadResult(endpoint, bucketName, path);
            uploadResult.setBucketName(bucketName);
            return uploadResult;
        } finally {
            try {
                ossClient.shutdown();
            } catch (Exception ignored) {
            }
        }
    }

    /**
     * 获取阿里云oss客户端
     *
     * @return OSS {@linkplain OSS}
     */
    private OSS getOssClient() {
        String accessKeyId = aliYunProperties.getAccessKeyId();
        String accessKeySecret = aliYunProperties.getAccessKeySecret();
        // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1（杭州）为例，Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
        String endpoint = aliYunProperties.getEndpoint();
        // 填写Endpoint对应的Region信息，例如cn-hangzhou。
        String region = aliYunProperties.getRegion();
        //使用代码嵌入的RAM用户的访问密钥配置访问凭证。
        CredentialsProvider credentialsProvider = new DefaultCredentialProvider(accessKeyId, accessKeySecret);
        ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
        clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
        return OSSClientBuilder.create()
                .endpoint(endpoint)
                .credentialsProvider(credentialsProvider)
                .clientConfiguration(clientBuilderConfiguration)
                .region(region)
                .build();
    }

    /**
     * 下载文件
     *
     * @param sysOss              文件上传记录
     * @param httpServletRequest  请求
     * @param httpServletResponse 响应
     * @return 响应体
     */
    @Override
    public ResponseEntity<StreamingResponseBody> download(SysOss sysOss, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        try {
            String originalName = sysOss.getOriginalName();
            OSS ossClient = getOssClient();
            String bucketName = sysOss.getBucketName();
            String rangeHeader = ServletUtil.getHeader(httpServletRequest, HttpHeaders.RANGE, StandardCharsets.UTF_8);
            long startByte;
            long endByte;
            Long contentLength = sysOss.getFileSize();
            if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
                String[] ranges = rangeHeader.substring(6).split("-");
                startByte = Long.parseLong(ranges[0].trim());
                endByte = (ranges.length > 1 && !ranges[1].isEmpty()) ? Long.parseLong(ranges[1].trim()) : contentLength - 1;
            } else {
                startByte = 0;
                endByte = contentLength - 1;
            }
            // 确保范围有效
            endByte = Math.min(endByte, contentLength - 1);
            String ossPath = sysOss.getOssPath();
            HttpHeaders httpHeaders = new HttpHeaders();
            MediaType mediaType1 = MediaTypeFactory.getMediaType(originalName)
                    .orElse(MediaType.APPLICATION_OCTET_STREAM);
            httpHeaders.setContentType(mediaType1);
            // 创建正确编码的文件名
            ContentDisposition contentDisposition = ContentDisposition
                    .builder("attachment")
                    .filename(originalName, StandardCharsets.UTF_8)
                    .build();
            httpHeaders.setContentDisposition(contentDisposition);
            //内容长度
            httpHeaders.setContentLength(contentLength);
            HttpStatus status = HttpStatus.OK;
            if (StringUtils.isNotNull(rangeHeader)) {
                //断点续传的位置
                List<HttpRange> ranges = new ArrayList<>();
                ranges.add(HttpRange.createByteRange(startByte, endByte));
                httpHeaders.setRange(ranges);
                status = HttpStatus.PARTIAL_CONTENT;
            }
            GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, ossPath);
            // 对于大小为1000 Bytes的文件，正常的字节范围为0~999。
            // 获取0~999字节范围内的数据，包括0和999，共1000个字节的数据。
            // 如果指定的范围无效（比如开始或结束位置的指定值为负数，或指定值大于文件大小），则下载整个文件。
            getObjectRequest.setRange(startByte, endByte);
            return ResponseEntity.status(status)
                    .headers(httpHeaders)
                    .body(outputStream -> {
                        OSSObject ossObject = ossClient.getObject(getObjectRequest);
                        try (InputStream inputStream = ossObject.getObjectContent();
                             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
                            IoUtil.copy(bufferedInputStream, outputStream);
                        } finally {
                            try {
                                ossClient.shutdown();
                            } catch (Exception ignored) {
                            }
                        }
                    });
        } catch (Exception e) {
            log.error("", e);
            httpServletResponse.setCharacterEncoding("utf-8");
            return ResponseEntity.internalServerError()
                    .contentType(MediaType.TEXT_PLAIN)
                    .body(o -> {
                        String errorMessage = "下载文件出现异常!";
                        byte[] bytes = errorMessage.getBytes(StandardCharsets.UTF_8);
                        o.write(bytes);
                    });
        }
    }
}
